#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "queue.h"
#include "qfunc.h"


/*
* NAME:int check_char(char ch)
* DESCRIPTION:check if input char is a letter
* INPUT:a character
* RETURN:if input is a character(case insensitive),return 1 
* esle return 0                          
*/
int check_char(char ch)
{
	if(('a'<=ch&&ch<='z')||('A'<=ch&&ch<='Z'))
	{
	     return 1;
	} else {
	     return 0;
	}
}


/*
* NAME:charType check_type(int ch)
* DESCRIPTION:check char is EOF,'\n','\0',printable or nonprintable
* INPUT:acsii number of a character
* RETURN:a charType varible,the type the character belong to                        
*/
charType check_type(int ch)
{
    switch(ch){
        case EOF:
	    return EOFILE;
	case '\n':
	    return EOLINE;
	case '\0':
	    return NULLCHAR;
	default:
	    if(isprint(ch))
	    {
	        return PRINTABLE;
	    } else {
  	        return NOPRINTABLE;
	    }
    }
}

/*
* NAME:Element* new_element(char* content,int size,int eof)
* DESCRIPTION:construct a new Element 
* INPUT:the pointer to string, length of string and if it
* the element notify other threads finish working
* the eof = 0 only used when the reader notify other threads
* should exit
* RETURN:a Element type pointer                        
*/
Element* new_element(char* content,int size,int eof)
{
	Element* e;
	e =  (Element*) malloc(sizeof(Element));
	if(NULL == e)/*if fail to allocat new memory for new element*/
    {
	    fprintf(stderr, "Couldn't malloc element\n");
	    exit(EXIT_FAILURE);
	}
	e->eof=eof;
	if(!eof)/*if eof !=0,need to init the new element*/
	{
        e->size = size;
		e->string = content;
	}

	return e;
}


/*
* NAME:void* reader(void* argv)
* DESCRIPTION:read from stdin and construct new memory for
* input string,construct new element to store the string 
* and put it in the first queue
* INPUT:void* type parameters,contain a input queue and
* out put queue,for reader, input queue should be NULL 
* RETURN:void* pointer                        
*/
void* reader(void* argv)
{	
	Args *args=argv;
	int ch;/*must use int*/
	int  ch_count, flushFlag;
	char* buff;
	Element *e,*e_end;/*e for common element,e_end for notify ending thread*/

	while (1)
	{
		ch_count=0;
		flushFlag=0;
		fflush(stdout);
		
        buff=(char*)malloc((MAXLINE+1)*sizeof(char));/*the last byte for '/0'*/
        if(NULL == buff) /*if fail to allocat new memory for new string*/
        {
		    fprintf(stderr, "Couldn't malloc space for string.\n");
		    exit(EXIT_FAILURE);
		}
		/*from malloc fucntion description,we know "The memory is not cleared"
		*(see 'man malloc')so we must init the memory before we use.
		*we chose to init buffer with '\0' */
		memset(buff,'\0',MAXLINE+1);
		
		while(((ch = getchar())!=EOF) && (ch!='\n'))
		{
		   /* getchar loop to disgard input */
			if(1 == flushFlag) continue;

			switch(check_type(ch))
			{
			    case PRINTABLE:
			        if(ch_count==MAXLINE)
				{
				    flushFlag = 1;
				} else {
				    /* remember buffer is zero indexed */
				    buff[ch_count++]=ch; /* put char in to string buffer */
				}
				break;
			    case NOPRINTABLE:
			    case NULLCHAR:
			        /* null char or non-printable detected flush current input */
				break;
			    default: 
			        ;/* don't care about other cases */
			}

		}

		if( (0==ch_count && '\n' == ch)&&(!flushFlag) )/* check if empty buffer and new line and not flushed */
		{
			fprintf(stderr,"Invaild Input,Input again!\n");/*display it when input a empty line*/
			continue; /* consider next input line */
		}

		switch(check_type(ch))
		{
		    case EOLINE:
			e=new_element(buff,ch_count,0);
			enqueue(args->outputQ, e);
			break;
		    case EOFILE:
			printf("EOF\n");
			if(ch_count!=0)/*for situation like: aaaaa^d*/
			{
				e=new_element(buff,ch_count,0);
				enqueue(args->outputQ, e);
				
                /*pass notification element'pointer into the first queue*/
				e_end=new_element(NULL,0,1);
				enqueue(args->outputQ, e_end);    
			}
			else/*if input from file */
            {
                /*pass notification element'pointer into the first queue*/
				e_end=new_element(NULL,0,1);
				enqueue(args->outputQ, e_end);    
			}
            printf("reader thread exit !\n");
            pthread_exit(NULL);	
            return NULL;   
		    default:
		        ; /* don't care about PRINTABLE/NOPRINTABLE/NULLCHAR here */
		}
	}
}


/*
* NAME:void* munch1 (void* argv)
* DESCRIPTION:read from reader thread's output queue(first queue),change
* all the ' 'in string to '*',pass the result into the second queue 
* INPUT:void* type parameters,contain a input queue and
* output queue,for munch1, input queue should be first queue,
* output queue should be second queue
* RETURN:void* pointer                        
*/
void* munch1(void* argv)
{     
	Element* ptr;
	int index;
	Args *args=argv;
	while(1) 
	{
		ptr=dequeue(args->inputQ);/*get element's pointer'*/
		if(1==ptr->eof)/*if get notification element,exit thread*/
		{
			break;
		}
		/*go through the string and change ' ' to '*' */
		for(index=0;index<ptr->size;index++)
		{
			if(' '==ptr->string[index])
			{
				ptr->string[index]='*';
			}
		}
		/*pass processed element'pointer into the second queue*/
		enqueue(args->outputQ, ptr);

	}
	/*pass notification element'pointer into the second queue*/
	enqueue(args->outputQ, ptr);
	printf("munch1 thread exit !\n");
	pthread_exit(NULL);
	return NULL;
}


/*
* NAME:void* munch2 (void* argv)
* DESCRIPTION:read from munch1 thread's output queue(second queue),change
* all lowercase char to upppercase char ,pass the result into the third queue 
* INPUT:void* type parameters,contain a input queue and
* output queue,for munch2, input queue should be second queue,
* output queue should be third queue
* RETURN:void* pointer                        
*/
void* munch2(void* argv)
{
	Element* ptr;
	int index;
	Args *args=argv;
	while(1)
	{
		ptr=dequeue(args->inputQ);/*get element's pointer'*/
		if(1==ptr->eof)/*if get notification element,exit thread*/
		{
			break;
		}
		for(index=0;index<ptr->size;index++)
		{
			if(check_char(ptr->string[index]))/*if is a letter*/
			{   
			    if(islower(ptr->string[index]))/*if is a lowercase letter*/
				    ptr->string[index]=toupper(ptr->string[index]);/*change to upppercase*/
			}
		}
		/*pass processed element'pointer into the third queue*/
		enqueue(args->outputQ, ptr);

	}
	printf("munch2 thread exit !\n");
	/*pass notification element'pointer into the third queue*/
	enqueue(args->outputQ, ptr);
	pthread_exit(NULL);
	return NULL;

}
/*
* NAME:void* writer (void* argv)
* DESCRIPTION:read from munch2 thread's output queue(third queue), 
* print result to stdout.And free the allocated memory
* count the number of processed string
* INPUT:void* type parameters,contain a input queue and
* output queue,for writer, input queue should be third queue,
* output queue should be NULL
* RETURN:void* pointer                        
*/

void* writer(void* argv)
{
	Element* ptr;
	Args *args=argv;
	int count=0;/*counter for string*/
	while(1)
	{
		ptr=dequeue(args->inputQ);
		if(1==ptr->eof)
		{
			free(ptr);/*if eof=0,don't nedd to free ptr->string'*/
			break;
		}
		printf("The final %d string is %s\n",++count,ptr->string);
		free(ptr->string);/*first free memory for string*/
		free(ptr);/*then free memory for element*/
	}
	printf("writer thread exit !\n");
	printf("total number of string is %d\n", count);/*the total number of strings*/
	pthread_exit(NULL);
}


int main(int argc, char const *argv[])
{

	pthread_t thread[4];/*four threads*/
	Queue *queue[3];/*3 queues,two threads share one queue*/
	int i=0;
	Args args[4];/*struct varible for parameters*/
	void* (*func[4])(void*);/*fucntion pointers*/
	
	/*init queues*/
	queue[0]=initQueue();
	queue[1]=initQueue();
	queue[2]=initQueue();

	/*init arguments,set each thread's input and output queue*/
	args[0].inputQ=NULL;
	args[0].outputQ=queue[0];
	args[1].inputQ=queue[0];
	args[1].outputQ=queue[1];
	args[2].inputQ=queue[1];
	args[2].outputQ=queue[2];
	args[3].inputQ=queue[2];
	args[3].outputQ=NULL;

	/*init function pointers*/
	func[0]=reader;
	func[1]=munch1;
	func[2]=munch2;
	func[3]=writer;

	for(i=0;i<4;i++)
	{
		if(pthread_create(&thread[i], NULL, func[i], (void*)&args[i]))
        {
			fprintf(stderr,"Could not create rthread \n");
			exit(EXIT_FAILURE);
		}

	}
	for(i=0;i<4;i++)
	{
		pthread_join(thread[i], NULL);/*if fail to create thread*/
	}

	return 0;
}
